across(.cols, .fns, ..., .names)Operaciones múltiples e iteración
Encuentro 4
La filosofía de trabajo de tidyverse se plantea nunca copiar y pegar más de dos veces el código escrito, pero cuando necesitamos realizar la misma operación en un conjunto de variables simultáneamente nos encontramos con este problema.
La solución, ofrecida dentro de dplyr, es un andamiaje que permite aplicar funciones y expresiones a varias columnas simultáneamente.
Es una forma de iteración, donde se repite la misma acción en diferentes objetos. En este caso los objetos serán columnas (variables) de la tabla de datos.
Las operaciones simultáneas pueden darse como transformación (dentro de un mutate()) o de resumen (dentro de un summarise())
Creación de múltiples columnas con mutate()
Resumiendo múltiples columnas con summarise()
La función across() es la encargada de dar soporte a estas operaciones múltiples (dplyr >= 1.0.0).
.cols = columnas a transformar
.fns = función o funciones para aplicar a cada columna de .cols
... = argumentos adicionales de las funciones especificadas anteriormente (ejemplo: na.rm = T)
.names = nombres de las columnas de salida. Aquí, {.col} es un marcador especial al que se le puede agregar el sufijo deseado.
Tomemos la siguiente tabla de datos ficticios (mostramos las primeras 4 observaciones):
# A tibble: 4 × 4
a b c d
<dbl> <dbl> <dbl> <dbl>
1 -0.560 1.22 -1.07 0.426
2 -0.230 0.360 -0.218 -0.295
3 1.56 0.401 -1.03 0.895
4 0.0705 0.111 -0.729 0.878
Supongamos que queremos calcular la media de cada variable…
Podríamos hacerlo repitiendo para cada variable
# A tibble: 1 × 4
a b c d
<dbl> <dbl> <dbl> <dbl>
1 0.0746 0.209 -0.425 0.322
Pero esto rompe la regla general que buscamos de nunca copiar y pegar más de dos veces…
Para solucionarlo aplicamos across() y realizamos el resumen simultáneo en una sola línea.
# A tibble: 1 × 4
a b c d
<dbl> <dbl> <dbl> <dbl>
1 0.0746 0.209 -0.425 0.322
Nótese que el primer argumento es el rango de nombres de variables y el segundo la función que aplicamos a todas ellas (nombres de funciones sin paréntesis).
El primer argumento de across() responde de la misma forma que la función select() y aplican también las funciones ayudantes de selección.
everything(): coincide con todas las variables.
group_cols(): seleccione todas las columnas de agrupación.
starts_with(): comienza con un prefijo.
ends_with(): termina con un sufijo.
contains(): contiene una cadena literal.
matches(): coincide con una expresión regular.
num_range(): coincide con un rango numérico como x01, x02, x03.
all_of(): coincide con nombres de variables en un vector de caracteres. Todos los nombres deben estar presentes; de lo contrario, se generará un error de fuera de límites.
any_of(): igual que all_of(), excepto que no se genera ningún error para los nombres que no existen.
where(): aplica una función a todas las variables y selecciona aquellas para las cuales la función regresa TRUE.
El argumento .cols también puede recibir construcciones booleanas utilizando los operadores conocidos como ! (negación) y conectores lógicos como & (AND) y | (OR) entre las funciones ayudantes de selección.
Por ejemplo:
Selecciona todas las columnas no numéricas, cuyo nombre comienza con “a”.
Hasta ahora vimos el ejemplo de aplicar una función simple como mean() a un grupo de variables.
Que sucede si entre los datos de esas variables hay valores NA?
Vamos a necesitar incorporar el argumento na.rm = TRUE a la función.
Donde lo hacemos dentro de un across()?
Supongamos que tenemos estos datos (mostramos algunas observaciones):
# A tibble: 4 × 4
a b c d
<dbl> <dbl> <dbl> <dbl>
1 1.56 -1.27 NA -0.473
2 -0.560 NA -1.05 -1.07
3 -0.230 1.22 0.238 -0.218
4 NA -0.446 1.29 -1.03
Vemos algunos valores NA entre las observaciones.
Si aplicamos el mismo código de across() anterior tendríamos como resultado:
# A tibble: 1 × 4
a b c d
<dbl> <dbl> <dbl> <dbl>
1 NA NA NA -0.703
Sería bueno que le pasaramos na.rm = TRUE a la función mean().
Existen dos formas sintácticas de hacerlo.
Una función estilo-purrr (tidyverse): ~ mean(.x, na.rm = TRUE)
Una función anónima (base): function(x) mean(x, na.rm = TRUE) ; o mejor en su forma de atajo: \(x) mean(x, na.rm = TRUE)
Para incorporar más de una función dentro de across() debemos incluirlas dentro de una lista [list()]
datos_na |>
summarise(
across(a:d, list(
media = \(x) mean(x, na.rm = TRUE),
n_na = \(x) sum(is.na(x))))
)# A tibble: 1 × 8
a_media a_n_na b_media b_n_na c_media c_n_na d_media d_n_na
<dbl> <int> <dbl> <int> <dbl> <int> <dbl> <int>
1 0.210 1 -0.293 1 0.161 2 -0.703 0
La lista contiene cada función a aplicar, bajo nombres definidos.
Observemos que los nombres de las variables resultado se componen del nombre de la columna, un guión bajo y el nombre definido de la función aplicada, para distinguir entre las múltiples funciones del across().
La estructura de estos nombres se pueden modificar con el argumento .names de across().
Los marcadores especiales para el nombre de columna es {.col} y para el nombre de la función definida es {.fn}.
Por ejemplo, podríamos invertir el orden predeterminado de los nombres del resumen.
Hasta ahora vimos como funciona la función across() dentro de un resumen (summarise) pero al comienzo también dijimos que se puede utilizar para transformaciones masivas de datos.
Para lograr esto la función se vincula con mutate() modificando las variables originales o bien creando nuevas variables si cambiamos su nombre con .names.
Aplicamos la función coalesce() para convertir los valores NA en ceros, transformando las variables originales.
Hacemos lo mismo pero cambiamos los nombres de las variables de salida del mutate() que van a coexistir con las originales.
# A tibble: 5 × 8
a b c d a_na_cero b_na_cero c_na_cero d_na_cero
<dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 1.56 -1.27 NA -0.473 1.56 -1.27 0 -0.473
2 -0.560 NA -1.05 -1.07 -0.560 0 -1.05 -1.07
3 -0.230 1.22 0.238 -0.218 -0.230 1.22 0.238 -0.218
4 NA -0.446 1.29 -1.03 0 -0.446 1.29 -1.03
5 0.0705 -0.687 NA -0.729 0.0705 -0.687 0 -0.729
En el caso de iteraciones similares para incluir dentro de la función filter() el paquete dplyr propone dos funciones específicas: if_any() e if_all().
En el primer caso, la función enmascara una repetición de OR lógicos y en la segunda una secuencia de AND lógicos.
# A tibble: 4 × 4
a b c d
<dbl> <dbl> <dbl> <dbl>
1 1.56 -1.27 NA -0.473
2 -0.560 NA -1.05 -1.07
3 NA -0.446 1.29 -1.03
4 0.0705 -0.687 NA -0.729
Es lo mismo que filter(is.na(a) | is.na(b) | is.na(c) | is.na(d))
# A tibble: 0 × 4
# ℹ 4 variables: a <dbl>, b <dbl>, c <dbl>, d <dbl>
Es lo mismo que filter(is.na(a) & is.na(b) & is.na(c) & is.na(d))
Las dos funciones de filtro trabajan con el mismo esquema que across(), por lo tanto se le puede aplicar una función o expresión de condición (debe devolver TRUE o FALSE)
for(): secuencia de elementos
while(): mientras una condición es verdadera
repeat(): repetición y control manual con break (uso peligroso)
map()for()for()Donde variable se reemplaza por un índice (generalmente 1 pero puede llamarse como deseemos) que recorrerá un vector desde 1 a una determinada longitud. (habitualmente 1:length(x)).
En cada vuelta la variable aumenta 1 hasta que alcanza el final del vector.
La variable i se utiliza en el cuerpo del for() para recorrer índices de objetos.
while()while()Donde condition es una condición lógica. Si se cumple el bucle continúa, de lo contrario se sale de él.
Dentro del cuerpo de la estructura debemos proceder a manejar los cambios en lo que se evalúa en la condición. Si eso no sucede, el bucle puede llegar a ser infinito.
El control es más “artesanal” que el bucle for() y depende completamente del usuario del lenguaje.
Paquete con herramientas que buscan remplazar las formas tradicionales de bucles iterativos otorgándole compatibilidad con tidy data (datos ordenados).
Se utiliza para aplicar funciones a vectores, dataframes y listas, dando lugar a la denominada “programación funcional” (FP).
El paquete se instala y activa con tidyverse.
Sus funciones son faciles de escribir pero más dificiles de entender para usuarios sin conocimientos de programación.
Las familia de funciones map() son similares en idea que la familia de funciones apply() de R base pero consistentes con el ecosistema tidyverse.
map()Instituto Nacional de Epidemiología